#ifndef ARRAY_EXPR_BOUND_CHECK_H
#define ARRAY_EXPR_BOUND_CHECK_H

struct unbound {};

struct lbound_cb
{
	typedef int value_type;
	
	template <class expr_T>
	inline static constexpr auto call( int dim, expr_T expr )
	-> decltype( expr.lbound(dim) )
	{ return expr.lbound( dim ); }
};

struct shape_cb
{
	typedef int value_type;
	
	template <class expr_T>
	inline static constexpr auto call( int dim, expr_T expr )
	-> decltype( expr.shape(dim) )
	{ return expr.shape( dim ); }
};

struct ordering_cb
{
	typedef unsigned int value_type;
	
	template <class expr_T>
	inline static constexpr auto call( int dim, expr_T expr )
	-> decltype( expr.ordering(dim) )
	{ return expr.ordering( dim ); }
};

template <class callback, class T>
struct tp_unpack_callback
{
	tp_unpack_callback( int dim ) 
	: dim( dim ), result( 0 ) {}
	
	template <class expr_T>
	constexpr inline void go( int i, expr_T& expr, std::true_type ) 
	{	return;  }

	template <class expr_T>
	inline void go( int i, expr_T& expr, std::false_type ) 
	{	result = callback::call( dim, expr );  }
	
	template <class expr_T>
	inline void operator()( int i, expr_T& expr ) 
	{
		typedef decltype( callback::call( dim, expr ) ) cb_return_T;
		typedef std::integral_constant<bool, std::is_same<cb_return_T, unbound>::value> is_unbound;
		return go( i, expr, is_unbound() );
	}
	
	int dim;
	T result;
};

template <class callback, class... expr_T>
struct bounding_checker
{
	typedef type_list<decltype( callback::call( int(), std::declval<expr_T>() ) )...> tlist;
	typedef typename callback::value_type value_type;
	typedef std::integral_constant<bool, all_same_as_type<unbound, tlist>::value> is_unbound;
	typedef typename std::conditional<is_unbound::value, unbound, value_type>::type return_T;
	
	inline static unbound
	get_value( int dim, std::tuple<expr_T...> exprs, std::true_type ) 
	{	return unbound(); }

	inline static return_T
	get_value( int dim, std::tuple<expr_T...> exprs, std::false_type ) 
	{	
		tp_unpack_callback<callback, value_type> cb( dim );
		tuple_for_each( cb, std::forward<decltype(exprs)>(exprs) );
		return cb.result;
	}

	inline static return_T
	get_value( int dim, std::tuple<expr_T...> exprs )
	{ return get_value( dim, exprs, is_unbound() ); }
};

#endif //!< ARRAY_EXPR_BOUND_CHECK_H